/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.rendering;

import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import org.apereo.portal.character.stream.CharacterEventBufferReader;
import org.apereo.portal.character.stream.CharacterEventReader;
import org.apereo.portal.character.stream.CharacterEventSource;
import org.apereo.portal.character.stream.events.CharacterEvent;
import org.apereo.portal.utils.cache.CacheKey;
import org.apereo.portal.xml.XmlUtilities;
import org.apereo.portal.xml.stream.ChunkingEventReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Converts a StAX event stream into a {@link CharacterEvent} stream. Breaking up the stream into
 * chunks based on specific elements
 */
public class StAXSerializingComponent implements CharacterPipelineComponent {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private XmlUtilities xmlUtilities;

    private StAXPipelineComponent wrappedComponent;
    private Map<String, CharacterEventSource> chunkingElements;
    private Map<Pattern, CharacterEventSource> chunkingPatternEventSources;
    private Pattern[] chunkingPatterns;

    @Autowired
    public void setXmlUtilities(XmlUtilities xmlUtilities) {
        this.xmlUtilities = xmlUtilities;
    }

    public void setWrappedComponent(StAXPipelineComponent wrappedComponent) {
        this.wrappedComponent = wrappedComponent;
    }

    public void setChunkingElements(Map<String, CharacterEventSource> chunkingElements) {
        this.chunkingElements = chunkingElements;
    }

    public void setChunkingPatterns(Map<String, CharacterEventSource> chunkingPatterns) {
        final Map<Pattern, CharacterEventSource> compiledChunkingPatternEventSources =
                new LinkedHashMap<Pattern, CharacterEventSource>();

        for (final Map.Entry<String, CharacterEventSource> chunkingPatternEntry :
                chunkingPatterns.entrySet()) {
            final String key = chunkingPatternEntry.getKey();
            final Pattern pattern = Pattern.compile(key);
            final CharacterEventSource value = chunkingPatternEntry.getValue();
            compiledChunkingPatternEventSources.put(pattern, value);
        }

        this.chunkingPatternEventSources = compiledChunkingPatternEventSources;
        this.chunkingPatterns =
                this.chunkingPatternEventSources
                        .keySet()
                        .toArray(new Pattern[this.chunkingPatternEventSources.size()]);
    }

    @Override
    public PipelineEventReader<CharacterEventReader, CharacterEvent> getEventReader(
            HttpServletRequest request, HttpServletResponse response) {
        final PipelineEventReader<XMLEventReader, XMLEvent> eventReader =
                this.wrappedComponent.getEventReader(request, response);

        // Writer shared by the ChunkingEventReader and the StAX Serializer
        final StringWriter writer = new StringWriter();

        final XMLOutputFactory outputFactory = this.xmlUtilities.getHtmlOutputFactory();
        final XMLEventWriter xmlEventWriter;
        try {
            xmlEventWriter = outputFactory.createXMLEventWriter(writer);
        } catch (XMLStreamException e) {
            throw new RuntimeException("Failed to create XMLEventWriter", e);
        }

        // Add the chunking wrapper to the XMLEventReader
        final XMLEventReader xmlEventReader = eventReader.getEventReader();
        final ChunkingEventReader chunkingEventReader =
                new ChunkingEventReader(
                        request,
                        this.chunkingElements,
                        this.chunkingPatternEventSources,
                        this.chunkingPatterns,
                        xmlEventReader,
                        xmlEventWriter,
                        writer);

        try {
            xmlEventWriter.add(chunkingEventReader);
            xmlEventWriter.flush();
            xmlEventWriter.close();
            chunkingEventReader.close();
        } catch (XMLStreamException e) {
            throw new RuntimeException("Failed to write events to Writer", e);
        }

        // Return the chunked data
        final List<CharacterEvent> characterEvents = chunkingEventReader.getCharacterEvents();
        final CharacterEventBufferReader characterEventReader =
                new CharacterEventBufferReader(characterEvents.listIterator());
        final Map<String, String> outputProperties = eventReader.getOutputProperties();
        return new PipelineEventReaderImpl<CharacterEventReader, CharacterEvent>(
                characterEventReader, outputProperties);
    }

    @Override
    public CacheKey getCacheKey(HttpServletRequest request, HttpServletResponse response) {
        return this.wrappedComponent.getCacheKey(request, response);
    }
}
